//错误类型
type ErrorType = string | Error;

//类似于Optional 方便自己的使用习惯
export class Result<T> {
  value: T;
  stack: ErrorType[]; //错误栈

  constructor(value: T, stack: ErrorType[]) {
    this.value = value;
    this.stack = stack;
  }

  static of<T>(value: T): Result<T> {
    if (Number.isNaN(value)) {
      return Result.error<T>(['the value is NaN!']);
    } else if (value === undefined || value === null) {
      return Result.error<T>(['the value is null or undefined!']);
    }
    return new Result(value, []);
  }

  static error<T>(stack: ErrorType[]): Result<T> {
    const a: any = undefined;
    return new Result(a as T, stack);
  }

  public getError(): ErrorType {
    const s = this.stack.at(this.stack.length - 1);
    if (s === undefined) {
      throw 'error not exist!';
    }
    return s;
  }

  toJSON() {
    return this.isPresent()
      ? { value: this.value }
      : { error: this.getError() };
  }

  isPresent() {
    return this.stack.length === 0;
  }

  isNotPresent() {
    return !this.isPresent();
  }

  map<R>(fn: (v: T) => R): Result<R> {
    if (this.isPresent()) {
      return Result.of(fn(this.value));
    }
    return Result.error<R>(this.stack);
  }

  filter(fn: (v: T) => boolean): Result<T> {
    if (this.isPresent()) {
      return fn(this.value)
        ? this
        : Result.error<T>([...this.stack, 'filter not pass']);
    } else {
      return this;
    }
  }

  ifError(e: ErrorType): Result<T> {
    return this.isPresent() ? this : Result.error<T>([...this.stack, e]);
  }

  flatMap<R>(fn: (v: T) => Result<R>): Result<R> {
    if (this.isPresent()) {
      return fn(this.value);
    }
    return Result.error<R>(this.stack);
  }

  get(): T {
    if (this.isNotPresent()) throw this.getError();
    return this.value;
  }

  orElse(t: T): T {
    return this.isPresent() ? this.value : t;
  }

  orElseGet(supply: () => T): T {
    return this.isPresent() ? this.value : supply();
  }

  ifPresent(fn: (t: T) => void): Result<T> {
    if (this.isPresent()) fn(this.value);
    return this;
  }

  ifNotPresent(fn: (t: ErrorType, stack: ErrorType[]) => void): Result<T> {
    if (this.isNotPresent()) fn(this.getError(), this.stack);
    return this;
  }

  log(s: (s: string) => void, split?: string) {
    this.ifNotPresent((_e, stack) => {
      s(stack.join(split ?? '||'));
    });
  }
}
